<?php
class Link {

    const TYPE_XML = 'xml';

    const TYPE_HTML = 'html';
    
    const TYPE_IMAGE = 'image';
    
    const TYPE_UNKNOWN = 'unknown';

    const ERROR_TIMEOUT = 'Operation timed out';

    const ERROR_403 = 'Access denied';

    const ERROR_404 = 'Page not found';

    const ERROR_500 = 'Server error';

    protected $_url;
    
    protected $_real_url;
    
    protected $_redirects = 0;
    
    protected $_request_headers = array (
                'User-Agent' => 'TO OpenGraph',
                'Accept'     => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                'Accept-Language' => 'en-us,en',
            );
    
    protected $_type;
    
    protected $_charset;
    
    protected $_error;

    protected $_parser;
    
    protected $_body;
    
    protected $_timeout = 50;
    
    protected $_debug = array();

    /**
     *
     * @var Response
     */
    protected $_response;
    
    /**
     *
     * @param string $url 
     */
    public function __construct($url)
    {
        $this->_add_debug("Init linker with url: $url");
        $this->_url = $this->_real_url = $url;
        $this->_pre_url();
        $this->_load();
    }

    public function load($url)
    {
        $this->_reset();

        $this->_add_debug("Link changed to: $url");
        $this->_url = $this->_real_url = $url;
        $this->_pre_url();
        $this->_load();
    }
    
    /**
     *
     * @param string $key
     * @return mixed 
     */
    public function headers($key = NULL)
    {
        return method_exists($this->_response, 'headers') ? $this->_response->headers($key) : NULL;
    }
    
    /**
     *
     * @return string
     */
    public function body()
    {
        if ( ! $this->_body)
        {
            $this->_body = $this->_response->body();
            $charset = $this->charset();
            try {
                if ($this->type() == self::TYPE_HTML)
                {
                    if (empty($charset)) 
                    {
                        $charset = mb_detect_encoding($this->_body);
                    }
                    if ( ! empty($charset)) 
                    {
                        $this->_body = mb_convert_encoding($this->_body, 'utf-8', $charset);
                    }

                    $this->_body = mb_convert_encoding($this->_body, 'html-entities', 'utf-8'); 
                }
            } catch (Exception $e){}
        }

        return $this->_body;
    }
    
    /**
     *
     * @return string
     */
    public function type()
    {
        if ( ! $this->_type)
        {
            $message = 'Detect type..';
            $info = array();
            $content_type = $this->headers('content-type');
            if (strpos($content_type, "xml") !== FALSE)
            {
                array_push($info, "Detected HTML by content-type header ($content_type)");
                $this->_type = self::TYPE_XML;
            }
            elseif (strpos($content_type, "html") !== FALSE)
            {
                array_push($info, "Detected HTML by content-type header ($content_type)");
                $this->_type = self::TYPE_HTML;
            } 
            elseif (strpos($content_type, "image") !== FALSE)
            {
                array_push($info, "Detected IMAGE by content-type header ($content_type)");
                $this->_type = self::TYPE_IMAGE;
            }
            elseif (Upload::type(array('name' => $this->_real_url, 'error' => UPLOAD_ERR_OK), array('jpg', 'jpeg', 'png', 'gif')))
            {
                array_push($info, "Detected IMAGE by url extension ({$this->_real_ur})");
                $this->_type = self::TYPE_IMAGE;
            }
            else
            {
                array_push($info, "!!! Type not detected ($content_type)");
                $this->_type = self::TYPE_UNKNOWN;
            }
            $this->_add_debug($message, $info);
        }
        return $this->_type;
    }
    
    /**
     *
     * @return string
     */
    public function charset()
    {
        if ( ! $this->_charset)
        {
            $message = 'Detect charset..';
            $info = array();
            if ($this->type() != self::TYPE_HTML)
            {
                return FALSE;
            }
            $this->_charset = 'utf-8';
            $content_type = $this->headers('content-type');
            if (strpos($content_type, "charset=") !== FALSE)
            {
                array_push($info, "Detected by content type ($content_type)");
                $this->_charset = str_replace("charset=", "", strstr($content_type, "charset="));
                $this->_charset = trim($this->_charset, "\";     ");
            }
            elseif (preg_match('|<meta.*charset=([a-zA-Z0-9-_]*).?>|Ui', $this->_body, $matches))
            {
                if (Arr::get($matches, 1))
                {
                    array_push($info, "Detected by meta tag (content-type=$content_type)");
                    $this->_charset = Arr::get($matches, 1);
                }
            }
            $this->_add_debug($message, $info);
        }
        
        return $this->_charset;
    }

    
    /**
     *
     * @return string|NULL
     */
    public function error()
    {
        return $this->_error;
    }
    
    public function parser()
    {
        if ( ! $this->_parser)
        {
            switch ($this->type())
            {
                case self::TYPE_XML:
                case self::TYPE_HTML:
                    $parser = Parser_Html::factory($this->body(), $this->_url, $this->_real_url);
                    break;
                case self::TYPE_IMAGE:
                    $parser = new Parser_Image($this->body(), $this->_url);
                    break;
                default:
                    $parser = new Parser_Unknow($this->_url);
            }
            
            $this->_add_debug('Init parser', array('class' => get_class($parser)));

            if (($redirect = $parser->need_redirect()))
            {
                $this->_add_debug('Parser want redirect', array('url' => $parser));
                $this->_redirects++;
                $this->_reset();
                $this->_real_url = $redirect;
                $this->_load();
                $parser = $this->parser();
                $this->_add_debug('ReInit parser', array('class' => get_class($parser)));
            }
            
            $this->_parser = $parser;
        }

        return $this->_parser;
    }
    
    
    protected function _pre_url()
    {
        $path = parse_url($this->_real_url, PHP_URL_PATH);
        $hash = parse_url($this->_real_url, PHP_URL_FRAGMENT);
        if (($path == '/') && ($hash{0} == '!'))
        {
            $this->_real_url = str_replace('#!/', '', $this->_real_url);
        }
    }

    protected function _get_domain()
    {
        $info = parse_url($this->_real_url);
        return $info['scheme'].'://'.$info['host'];
    }


    protected function _load()
    {
        try {
            $this->_add_debug("Try load {$this->_real_url}...");
            $this->_real_url = str_replace(' ', '%20', $this->_real_url);
            $this->_request_headers['Referer'] = $this->_get_domain();
            $request = Request::factory($this->_real_url)->headers($this->_request_headers);
            $request->client()->options(CURLOPT_TIMEOUT, $this->_timeout);
            $this->_response = $request->execute();

            if (($location = $this->_response->headers('location')))
            {
                $this->_add_debug("Redirect detected", array('headers' => $this->_response->headers()));
                if (strpos($location, '//') === 0)
                {
                    $location = "http:$location";
                }
                
                if (strpos($location, 'http') !== 0)
                {
                    $host = parse_url($this->_real_url, PHP_URL_HOST);
                    $location = "http://".$host.'/'.ltrim($location, '/');
                }

                if ($this->_response->headers('set-cookie'))
                {
                    $this->headers('Cookie', $this->_response->headers('set-cookie'));
                }

                $this->_redirects++;
                $this->_request_headers['Referer'] = $this->_real_url;
                $this->_real_url = $location;
                $this->_load();
            }

            if ($this->_response->status() != 200)
            {
                $this->_add_debug("Bad status, generate error..", array('status' => $this->_response->status(), 'headers' => $this->_response->headers()));
                $this->_error();
            }
        } catch (Exception $e) {
            $message = null;
            if (strstr($e->getMessage(), self::ERROR_TIMEOUT))
            {
                $message = self::ERROR_TIMEOUT;
            }

            $this->_error($message);
        }
    }
    
    
    protected function _error($error = NULL)
    {
        if ($error)
        {
            $this->_error = $error;
            return;
        }

        if ( ! $this->_response)
        {
            $this->_error = 'Invalid url';
            return;
        }
        
        switch ($this->_response->status())
        {
            case 403:
                $this->_error = self::ERROR_403;
                break;
            case 404:
                $this->_error = self::ERROR_404;
                break;
            default:
                $this->_error = self::ERROR_500;
                break;
        }
    }
    
    protected function _reset()
    {
        $this->_body = 
        $this->_charset =
        $this->_error = 
        $this->_parser = 
        $this->_type = NULL;
    }

    public function check_keywords()
    {
        $keywords = $this->_parser->get_keywords();
        if (count($keywords) > 0)
        {
            $this->_add_debug('Category', array('keywords' => $keywords));
        }
    }

    protected function _add_debug($message, $info = NULL)
    {
        if ($info)
        {
            $data = array (
                'message' => $message,
                'info' => $info
            );
        }
        else
        {
            $data = $message;
        }
        array_push($this->_debug, $data);
    }
    
    
    /**
     * @return array
     */
    public function get_debug()
    {
        return $this->_debug;
    }
    
}